文章目录
  1. 1. NDK & JNI
  2. 2. JNI中的映射关系
  3. 3. NDK用到的C/C++运行库
  4. 4. References

NDK & JNI

NDK和JNI的关系是: Java通过JNI的方式调用由NDK编译生成的本地库.
JNI是独立于NDK的一个技术, NDK是帮助编译c/cpp文件的一个工具.

在JNI的c文件中如果用到了#include <utils/Log.h>

然后用NDK 编译的时候会提示error: utils/Log.h: No such file or directory
这个是因为这个头文件在Android源码的frameworks/base/include中,不在SDK或NDK中

如果想要使用NDK的LOG功能的话

1—–修改Android.mk文件配置,添加如下语句,这是载入liblog库,其中$(SYSROOT)是NDK中platform的路径

LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog

2—–在.c文件中修改为如下语句。这是引入NDK中的日志头文件,位置在$(SYSROOT)/usr/include

1
#include<android/log.h>

3—–使用方法。其中这后面的方法在头文件中可以找到定义

1
2
3
4
#define LOG_TAG "debug"
#define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt, ##args)
#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, fmt, ##args)
#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, fmt, ##args)

4—-打印语句

1
2
3
LOGI("test log!!!!");

LOGI("the string is: %s \n",buff);

5—-错误输出到日志

1
LOGI(strerror(errno))

如果想要开启NDK的Debug功能,需要按照下列步骤操作。

JNI中的映射关系

基本类型

Java 类型 jni本地类型 描述
boolean jboolean C/C++ unsigned 8 bits
byte jbyte C/C++ signed 8 bits
char jchar C/C++ unsigned 16 bits
short jshort C/C++ signed 16 bits
int jint C/C++ signed 32 bits
long jlong C/C++ signed 64 bits
float jfloat C/C++ 32位浮点型
double jdouble C/C++ 64位浮点型
void void N/A

命名规则就是java基本类型加j前缀.

引用类型和数组类型也差不多:
引用类型

Object jobject 任何Java对象,或者没有对应java类型的对象
Class jclass class对象
String jstring 字符串对象

数组类型

boolean[] jbooleanArray 布尔型数组 unsigned
byte[] jbyteArray 比特型数组 signed
char[] jcharArray 字符型数组 unsigned
short[] jshortArray 短整型数组 signed
int[] jintArray 整型数组 signed
long[] jlongArray 长整型数组 signed
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
Object[] jobjectArray 任何对象的数组

数组类型多个Array后缀.

JNI函数签名

1
JNIEXPORT jstring JNICALL Java_com_study_jnilearn_HelloWorld_sayHello(JNIEnv *, jclass, jstring);

第一个参数: JNIEnv* 是定义任意 native 函数的第一个参数(包括调用 JNI 的 RegisterNatives 函数注册的函数), 指向 JVM 函数表的指针, 函数表中的每一个入口指向一个 JNI 函数, 每个函数用于访问 JVM 中特定的数据结构.
第二个参数: 调用 Java 中 native 方法的实例或 Class 对象, 如果这个 native 方法是实例方法, 则该参数是 jobject,如果是静态方法,则是 jclass.
第三个参数: Java 对应 JNI 中的数据类型, Java 中 String 类型对应 JNI 的 jstring 类型. (前面已介绍 JAVA 与 JNI 数据类型的映射关系)

函数签名也可以手动动态映射, 通过

1
2
3
4
static JNINativeMethod gMethods[] = {
// 这里写映射关系数组
// {"native函数名", "参数列表", 对应的JNI方法}
};

其中参数列表有个缩写映射规则,要用缩写表示Java类型.
当参数为引用类型的时候,参数类型的标示的格式为”L完整路径名;”,前面L后面分号.如果是数组就是在成员类型前面加[,有多少层加多少个[.例如String对应的就是(Ljava/lang/String;)

类型标志 Java类型
Z boolean
F float
B byte
D double
C char
Ljava/lang/String String
S short
[I Int[]
I int
[Ljava/lang/object Object[]
J long

javap可以生成函数签名. javap -s -p xxx. 参数含义参见help.

JNI层就相当于是在用C写东西了, 所以语法也是C的语法.
对于基本类型, 可以直接强制类型转换成对应长度的C/C++类型(事实上, 查看jni基本类型的定义可以发现, 它们都是c基本类型的typedef).
对于数组类型, 需要使用JNIEnv提供的方法, 简单类型的数组会把其指针直接暴露给本地代码. 一系列GetXXXArrayElements函数可以把Java数组作为参数, 返回一个指向对应的本地类型的数组指针.

函数 Java 数组类型 本地类型
GetBooleanArrayElements jbooleanArray jboolean
GetByteArrayElements jbyteArray jbyte
GetCharArrayElements jcharArray jchar
GetShortArrayElements jshortArray jshort
GetIntArrayElements jintArray jint
GetLongArrayElements jlongArray jlong
GetFloatArrayElements jfloatArray jfloat
GetDoubleArrayElements jdoubleArray jdouble

使用完之后要调用对应的ReleaseXXXArrayElements, 参数是对应的数组和前面GetXXXArrayElements返回的指针. 这个函数会把你对本地数组做的变化复制到Java数组中, 然后释放相关资源.
JNIEnv还有一些别的方法:

1
2
3
4
5
6
7
8
9
10
11
GetArrayLength

NewXXXArray

GetXXXArrayElements

ReleaseXXXArrayElements

GetXXXArrayRegion

SetXXXArrayRegion

这些方法都是Env预定义的方法, 帮助你在C/C++的Array和Java的Array之间转换.

NDK用到的C/C++运行库

你在Android.mk文件中可以指定LOCAL_CPP_EXTENSION来告诉编译器, 把什么后缀的文件当做C++源文件来编译. 对于C和C++会采用不同的编译器. 不过,编译过后的C++程序想要执行,还必须要有C++运行环境(C++ Runtime)的支持。默认情况下,Android NDK会使用一个非常迷你的C++运行环境,称做“libstdc++”。这个运行环境几乎什么都没有,不支持异常和RTTI(RunTime Type Information,即运行时类型识别),甚至连C++标准库也没有。


c++ runtime library

.a是静态库, .so是动态库, 也叫共享库. 通过Application.mkAPP_STL可以配置想要用什么运行库, 需要注意的是Android源码目录的ndk子目录下面, 就有各个目标平台和架构的运行库, Android 8(Android 2.2)之前, 是只有默认的那个库.

最后,还有一点需要注意,如果你的JNI功能都是包含在一个模块(也就是程序中只有一个.so文件)中的话,可以考虑用静态库C++运行时库的形式。而如果你的程序中包含了多个模块,请尽量使用动态C++运行时库。

在用动态C++运行时库的时候,相应的动态库.so文件(默认的C++运行时库除外)会随你的程序一起发布出去,位于apk文件中的lib目录下。

如果你的目标Android系统是4.3之前,那么你必须要在加载所有你自己的模块之前,显式的加载C++动态运行时库。

References

JNI版本加入的时间和版本间差异

文章目录
  1. 1. NDK & JNI
  2. 2. JNI中的映射关系
  3. 3. NDK用到的C/C++运行库
  4. 4. References